package com.maptool.map;

import com.esri.arcgisruntime.geometry.AreaUnit;
import com.esri.arcgisruntime.geometry.AreaUnitId;
import com.esri.arcgisruntime.geometry.GeodeticCurveType;
import com.esri.arcgisruntime.geometry.Geometry;
import com.esri.arcgisruntime.geometry.GeometryEngine;
import com.esri.arcgisruntime.geometry.GeometryType;
import com.esri.arcgisruntime.geometry.ImmutablePart;
import com.esri.arcgisruntime.geometry.ImmutablePartCollection;
import com.esri.arcgisruntime.geometry.LinearUnit;
import com.esri.arcgisruntime.geometry.LinearUnitId;
import com.esri.arcgisruntime.geometry.Point;
import com.esri.arcgisruntime.geometry.PointCollection;
import com.esri.arcgisruntime.geometry.Polygon;
import com.esri.arcgisruntime.geometry.Polyline;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;

/**
 * 矢量utils
 */
public class GeometryUtils {

    /**
     * 计算稿两个点的距离，单位返回米
     *
     * @param beginPoint
     * @param lastPoint
     * @return
     */
    public static double distance(Point beginPoint, Point lastPoint) {
        PointCollection points = new PointCollection(beginPoint.getSpatialReference());
        points.add(beginPoint);
        points.add(lastPoint);
        Polyline polylineLength = new Polyline(points);
        return GeometryEngine.lengthGeodetic(polylineLength, new LinearUnit(LinearUnitId.METERS), GeodeticCurveType.GREAT_ELLIPTIC);
    }

    /**
     * 计算面积，返回double类型
     *
     * @param polygon
     * @return
     */
    public static double areaDouble(Polygon polygon) {
        return area(polygon);
    }

    /**
     * 计算面积保留后面两位小数返回字符串
     *
     * @param polygon
     * @return
     */
    public static String areaString(Polygon polygon) {
        double geodetic = area(polygon);
        if (geodetic == 0) {
            return "0";
        }
        DecimalFormat distancedf = new DecimalFormat("#0.00");
        return distancedf.format(geodetic * 0.0015);
    }

    private static Double area(Geometry originalGeometry) {
        try {
            // 调用带超时的方法，设置超时时间为 2 秒
            Double result = runWithTimeout(() -> {
                // 模拟一个耗时操作
                return GeometryEngine.areaGeodetic((Polygon) originalGeometry, new AreaUnit(AreaUnitId.SQUARE_METERS), GeodeticCurveType.GREAT_ELLIPTIC);
            }, 2, TimeUnit.SECONDS);

            System.out.println("结果: " + result);
            return result;
        } catch (TimeoutException e) {
            System.out.println("操作超时！");
        } catch (InterruptedException | ExecutionException e) {
            System.out.println("操作被中断或执行出错: " + e.getMessage());
        }
        return 0.0;
    }

    /**
     * 尖角判断
     * 根据角度阈值查找面要素中的异常点,角度默认小于15度
     *
     * @param polygon 待检查的面要素
     * @return 异常点列表
     */
    public static List<Point> isSharpCorner(Polygon polygon) {
        return sharpCorner(polygon, 15, 0);
    }

    /**
     * 尖角判断
     * 根据角度阈值查找面要素中的异常点
     *
     * @param polygon        待检查的面要素
     * @param angleThreshold 角度阈值（单位：度）
     * @return 异常点列表
     */
    public static List<Point> isSharpCorner(Polygon polygon, double angleThreshold) {
        return sharpCorner(polygon, angleThreshold, 0);
    }

    /**
     * 尖角判断
     * 根据角度阈值查找面要素中的异常点
     *
     * @param polygon  待检查的面要素
     * @param distance 边线长度，单位米
     * @return 异常点列表
     */
    public static List<Point> isSharpCornerDist(Polygon polygon, double distance) {
        return sharpCorner(polygon, 15, distance);
    }

    /**
     * 尖角判断
     * 根据角度阈值查找面要素中的异常点
     *
     * @param polygon        待检查的面要素
     * @param angleThreshold 角度阈值（单位：度）
     * @param distance       边线长度，单位米
     * @return 异常点列表
     */
    public static List<Point> isSharpCorner(Polygon polygon, double angleThreshold, double distance) {
        return sharpCorner(polygon, angleThreshold, distance);
    }

    //尖角* @param distance       角度线大于多少米后判断未尖角
    private static List<Point> sharpCorner(Polygon polygon, double angleThreshold, double distance) {
        List<Point> outliers = new ArrayList<>();
        if (polygon == null || polygon.isEmpty()) {
            return outliers;
        }

        // 获取面的所有部分
        ImmutablePartCollection parts = polygon.getParts();
        List<Point> pointList = new ArrayList<>();
        // 遍历每个部分
        for (ImmutablePart part : parts) {
            // 获取该部分的所有点
            Iterable<Point> points = part.getPoints();
            for (Point point : points) {
                pointList.add(point);
            }
        }
        int pointCount = pointList.size();

        if (pointCount < 3) {
            return outliers;
        }

        // 检查每个顶点处的内角
        for (int i = 0; i < pointCount; i++) {
            Point prev = pointList.get((i - 1 + pointCount) % pointCount);
            Point current = pointList.get(i);
            Point next = pointList.get((i + 1) % pointCount);

            double angle = calculateAngle(prev, current, next);
            double distance1 = distance(prev, current);
            double distance2 = distance(next, current);
            if (distance < distance1 || distance < distance2) {
                if (angle < angleThreshold) {
                    outliers.add(current);
                    outliers.add(prev);
                    outliers.add(next);
                }
            }
        }

        return outliers;
    }

    /**
     * 计算三个点所形成的内角（单位：度）
     *
     * @param p1 第一个点
     * @param p2 第二个点（顶点）
     * @param p3 第三个点
     * @return 内角的度数
     */
    private static double calculateAngle(Point p1, Point p2, Point p3) {
        double dx1 = p1.getX() - p2.getX();
        double dy1 = p1.getY() - p2.getY();
        double dx2 = p3.getX() - p2.getX();
        double dy2 = p3.getY() - p2.getY();

        double dotProduct = dx1 * dx2 + dy1 * dy2;
        double magnitude1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
        double magnitude2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);

        return Math.acos(dotProduct / (magnitude1 * magnitude2)) * (180 / Math.PI);
    }


    /**
     * 面自相交判断
     *
     * @param polygon 传入图形
     * @return
     */
    public static boolean geometryIntersect(Polygon polygon) {
        Polyline polyline = polygon.toPolyline();
        return instarsect(polyline);
    }

    /**
     * 线自相交判断
     *
     * @param polyline 传入图形
     * @return
     */
    public static boolean geometryIntersect(Polyline polyline) {
        return instarsect(polyline);
    }

    /**
     * 面自相交判断
     *
     * @param polygon 传入图形
     * @return 返回相交的图形，没有相交就没有图形 null
     */
    public static List<Point> geometryIntersectBack(Polygon polygon) {
        Polyline polyline = polygon.toPolyline();
        return intersect(polyline);
    }

    /**
     * 线自相交判断
     *
     * @param polyline 传入图形
     * @return 返回相交的集合点
     */
    public static List<Point> geometryIntersectBack(Polyline polyline) {
        return intersect(polyline);
    }

    //自相交通用方法
    private static boolean instarsect(Polyline polyline) {
        ImmutablePartCollection parts = polyline.getParts();
        //原始点集合
        List<Point> primitivePoint = new ArrayList<>();
        //取出线中的所有节点
        for (ImmutablePart part : parts) {
            Iterable<Point> pointIterable = part.getPoints();
            for (Point point : pointIterable) {
                primitivePoint.add(point);
            }
        }
        /**
         * 使用线和面进行相交处理，
         * 1、如果相交出来的相交图形的节点大于原始线的所有节点，则有相交的点----->相交
         * 2、如果相交的节点点是原始数量节点------>不相交
         */
        List<Point> intersectionsPoint = new ArrayList<>();//相交后的点集合
        List<Geometry> intersections = GeometryEngine.intersections(polyline, polyline);
        for (Geometry geometry : intersections) {
            GeometryType geometryType = geometry.getGeometryType();
            if (geometryType == GeometryType.POINT) {
                Point point = (Point) geometry;
                intersectionsPoint.add(point);
            } else if (geometryType == GeometryType.POLYLINE) {
                Polyline polyline1 = (Polyline) geometry;
                ImmutablePartCollection parts1 = polyline1.getParts();
                for (ImmutablePart partline : parts1) {
                    Iterable<Point> points1 = partline.getPoints();
                    for (Point point : points1) {
                        intersectionsPoint.add(point);
                    }
                }

            } else if (geometryType == GeometryType.POLYGON) {
                Polygon polygon1 = (Polygon) geometry;
                ImmutablePartCollection parts1 = polygon1.getParts();
                for (ImmutablePart partgon : parts1) {
                    Iterable<Point> points1 = partgon.getPoints();
                    for (Point point : points1) {
                        intersectionsPoint.add(point);
                    }
                }
            }
        }

        //原始点集合数量
        int startCount = primitivePoint.size();
        //相交后的点集合数量
        int endCount = intersectionsPoint.size();
        return endCount > startCount;
    }

    /**
     * 地块自相交
     *
     * @param polyline 传入图形
     * @return 返回相交的集合点
     */
    private static List<Point> intersect(Polyline polyline) {

        List<Point> list = new ArrayList<>();
        ImmutablePartCollection parts = polyline.getParts();
        //原始点集合
        List<Point> primitivePoint = new ArrayList<>();
        //取出线中的所有节点
        for (ImmutablePart part : parts) {
            Iterable<Point> pointIterable = part.getPoints();
            for (Point point : pointIterable) {
                primitivePoint.add(point);
            }
        }
        /**
         * 使用线和面进行相交处理，
         * 1、如果相交出来的相交图形的节点大于原始线的所有节点，则有相交的点----->相交
         * 2、如果相交的节点点是原始数量节点------>不相交
         */
        List<Point> intersectionsPoint = new ArrayList<>();//相交后的点集合
        List<Geometry> intersections = GeometryEngine.intersections(polyline, polyline);
        for (Geometry geometry : intersections) {
            GeometryType geometryType = geometry.getGeometryType();
            if (geometryType == GeometryType.POINT) {
                Point point = (Point) geometry;
                intersectionsPoint.add(point);
            } else if (geometryType == GeometryType.POLYLINE) {
                Polyline polyline1 = (Polyline) geometry;
                ImmutablePartCollection parts1 = polyline1.getParts();
                for (ImmutablePart partline : parts1) {
                    Iterable<Point> points1 = partline.getPoints();
                    for (Point point : points1) {
                        intersectionsPoint.add(point);
                    }
                }

            } else if (geometryType == GeometryType.POLYGON) {
                Polygon polygon1 = (Polygon) geometry;
                ImmutablePartCollection parts1 = polygon1.getParts();
                for (ImmutablePart partgon : parts1) {
                    Iterable<Point> points1 = partgon.getPoints();
                    for (Point point : points1) {
                        intersectionsPoint.add(point);
                    }
                }
            }
        }

        //原始点集合数量
        int startCount = primitivePoint.size();
        //相交后的点集合数量
        int endCount = intersectionsPoint.size();

        boolean b = endCount > startCount;
        if (b) {
            List<Point> difference = getDifference(primitivePoint, intersectionsPoint);
            Set<Point> set = new HashSet<>(difference);
            list.addAll(set);
        }
        return list;

    }

    private static List<Point> getDifference(List<Point> list1, List<Point> list2) {
        List<Point> difference1 = list1.stream()
                .filter(e -> !list2.contains(e))
                .collect(Collectors.toList());
        List<Point> difference2 = list2.stream()
                .filter(e -> !list1.contains(e))
                .collect(Collectors.toList());
        List<Point> result = new ArrayList<>(difference1);
        result.addAll(difference2);
        return result;
    }


    /**
     * 修边
     *
     * @param pPolygon 需要处理的面
     * @param polyline 修正线
     * @return 返回修边后的图形，有可能是多部件修边的，所以会返回多个
     */
    public static List<Polygon> reshapePolygon(Polygon pPolygon, Polyline polyline) {
        Geometry geometry = GeometryEngine.reshape(pPolygon, polyline);
        geometry = GeometryEngine.simplify(geometry);
        Polygon geometry1 = (Polygon) geometry;
        ImmutablePartCollection parts = geometry1.getParts();
        int size = parts.size();
        List<Polygon> list = new ArrayList<>();
        if (size > 1) {
            /**
             * 出现多部件
             */
            for (ImmutablePart part : parts) {
                PointCollection pointCollection = new PointCollection(pPolygon.getSpatialReference());
                Iterable<Point> points = part.getPoints();
                for (Point point : points) {
                    pointCollection.add(point);
                }
                Polygon polygon = new Polygon(pointCollection);
                list.add(polygon);
            }

        } else {
            list.add(geometry1);
        }
        return list;
    }


    /**
     * 执行一个带超时的可调用任务
     *
     * @param task    要执行的任务
     * @param timeout 超时时间
     * @param unit    超时时间单位
     * @return 任务执行结果
     * @throws TimeoutException     如果任务执行超时
     * @throws ExecutionException   如果任务执行过程中抛出异常
     * @throws InterruptedException 如果当前线程在等待过程中被中断
     */
    private static <T> T runWithTimeout(Callable<T> task, long timeout, TimeUnit unit)
            throws TimeoutException, ExecutionException, InterruptedException {
        // 创建一个单线程执行器
        ExecutorService executor = Executors.newSingleThreadExecutor();
        try {
            // 提交任务并获取Future对象
            Future<T> future = executor.submit(task);
            try {
                // 等待任务完成，最多等待指定的超时时间
                return future.get(timeout, unit);
            } finally {
                // 无论任务是否完成，都尝试取消任务
                future.cancel(true);
            }
        } finally {
            // 关闭执行器
            executor.shutdownNow();
        }
    }
}
